/*
 * Copyright 2009 Alberto Gimeno <gimenete at gmail.com>
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package siena.gae;

import static com.google.appengine.api.datastore.FetchOptions.Builder.withChunkSize;
import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import siena.ClassInfo;
import siena.IterableQuery;
import siena.Query;
import siena.SienaException;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;

public class GaeQuery<T> implements Query<T> {

	private static final Map<String, FilterOperator> supportedOperators = new HashMap<String, FilterOperator>() {
		private static final long serialVersionUID = 1L;
		{
			put("=",  FilterOperator.EQUAL);
			put("<",  FilterOperator.LESS_THAN);
			put(">",  FilterOperator.GREATER_THAN);
			put("<=", FilterOperator.LESS_THAN_OR_EQUAL);
			put(">=", FilterOperator.GREATER_THAN_OR_EQUAL);
			put("IN", FilterOperator.IN);
		}
	};
	
	private GaePersistenceManager pm;
	private com.google.appengine.api.datastore.Query q;
	private Class<T> clazz;
	
	private int nextOffset;

	public GaeQuery(GaePersistenceManager pm, Class<T> clazz) {
		this.clazz = clazz;
		this.pm = pm;
		this.q = new com.google.appengine.api.datastore.Query(
				ClassInfo.getClassInfo(clazz).tableName);
	}

	public int count() {
		return fetch().size();
	}

	public int count(int limit) {
		return fetch(limit).size(); // FIXME don't call fetch()
	}

	public int count(int limit, Object offset) {
		return fetch(limit, offset).size(); // FIXME don't call fetch()
	}
	
	private List<T> map(int offset, List<Entity> entities) {
		List<T> result = pm.mapEntities(entities, clazz);
		nextOffset = offset + result.size();
		return result;
	}

	public List<T> fetch() {
		return map(0, prepare().asList(withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE)));
	}

	public List<T> fetch(int limit) {
		return map(0, prepare().asList(withLimit(limit)));
	}

	public List<T> fetch(int limit, Object offset) {
		return map((Integer) offset, prepare().asList(withLimit(limit).offset((Integer) offset)));
	}
	
	private PreparedQuery prepare() {
		return pm.getDatastoreService().prepare(q);
	}

	public Query<T> filter(String field, Object value) {
		FilterOperator op = FilterOperator.EQUAL;
		for (String operator : supportedOperators.keySet()) {
			if(field.endsWith(operator)) {
				op = supportedOperators.get(operator);
				field = field.substring(0, field.length()-operator.length());
				break;
			}
		}
		field = field.trim();
		
		Field f = null;
		try {
			f = clazz.getDeclaredField(field);
		} catch (Exception e) {
			throw new SienaException(e);
		}
		
		String propertyName = ClassInfo.getColumnNames(f)[0];
		
		if(value != null && ClassInfo.isModel(value.getClass())) {
			Key key = pm.getKey(value);
			q.addFilter(propertyName, op, key);
		} else {
			if(ClassInfo.isId(f)) {
				if(value instanceof String) {
					value = Long.parseLong(value.toString());
				}
				Key key = KeyFactory.createKey(ClassInfo.getClassInfo(clazz).tableName, (Long) value);
				q.addFilter(Entity.KEY_RESERVED_PROPERTY, op, key);
			} else {
				q.addFilter(propertyName, op, value);
			}
		}
		return this;
	}

	public T get() {
		List<T> list = fetch(1);
		if(list.isEmpty())
			return null;
		return list.get(0);
	}

	public Iterable<T> iter(String field, int max) {
		return new IterableQuery<T>(this, max, field);
	}

	public Query<T> order(String field) {
		SortDirection dir = SortDirection.ASCENDING;
		if(field.startsWith("-")) {
			field = field.substring(1);
			dir = SortDirection.DESCENDING;
		}
		Field f;
		try {
			f = clazz.getDeclaredField(field);
		} catch (Exception e) {
			throw new SienaException(e);
		}
		if(ClassInfo.isId(f)) {
			q.addSort(Entity.KEY_RESERVED_PROPERTY);
		} else {
			q.addSort(ClassInfo.getColumnNames(f)[0], dir);
		}
		return this;
	}

	public Query<T> search(String match, boolean inBooleanMode,
			String... fieldNames) {
		// TODO Auto-generated method stub
		return null;
	}
	
	public GaeQuery<T> clone() {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public Object nextOffset() {
		return nextOffset;
	}

	@Override
	public int delete() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public List<T> fetchKeys() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<T> fetchKeys(int limit) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<T> fetchKeys(int limit, Object offset) {
		// TODO Auto-generated method stub
		return null;
	}

}
